JavaScript作用域和闭包
Q:解释 JavaScript 中的词法作用域(Lexical Scoping)和闭包(Closure)。闭包是如何工作的,它们在实际开发中有哪些用途?
A:词法作用域是指一个函数的内部作用域,闭包是函数以及其周围的此法环境,它通常是一个函数,这个函数能访问外部的变量,因此这个变量一直存续在函数存续期间;在实际开发过程中,闭包可以用于将函数和他词法环境绑定,比如计算长方形的面积,当他的宽固定时,就可以写作:function calcArea(width){ return (height)=>{ return width*height; } } const area = calcArea(10); area(20);
另外,闭包也用于节流、防抖等,但要注意错误的使用闭包会造成内存泄漏。
C:实际应用:数据封装(模拟私有变量)、函数柯里化、事件处理器和回调函数异步编程
Q:解释Promise
和async/await
在 JavaScript 中的作用。如何使用它们处理异步操作?请给出一个使用async/await
处理 HTTP 请求并捕获错误的示例代码。
A:由于js是单线程的,所以一些网络请求等需求需要异步实现,最早使用的方式是回调函数,但是当一个回调函数依赖于另一个回调函数的时候,就会造成回调地狱,promise为了解决这一问题出现,promise的构造函数接受resolve和reject两个参数;async/await是promise的语法糖,实现了用同步的写法实现异步请求。
C:promise是一个代表了异步操作最终完成或失败的对象。示例代码:
async function getData(){ try{ const res = await axios.get("xx"); console.log(res); }catch(err){ console.log(err); } } getData();
CSS布局
Q:描述 Flexbox 和 Grid 布局的主要区别。你更倾向于在什么情况下使用 Flexbox,什么情况下使用 Grid?
A:flex是弹性盒子,gird是栅格布局,flex较为灵活。
C:flexbox是一种布局方法,意味着它能够处理元素在一个方向上的空间分配,flex设计的初衷是为了提供一种有效的方式来布局、对齐和分配在容器中的项目空间,即使大小是未知或动态变化的。 一维布局;
grid是二维布局。前端性能优化
Q:列举至少五种你可以用来提高网页性能的方法或技术。请解释为什么这些方法会影响性能,并且你是如何决定哪些方法最适合当前项目的。
A:1. 使用精灵图,当前端使用较多小尺寸icon的时候,可以考虑把这些合并成一个较大的精灵图,避免多次请求服务器资源;2. 首屏加载优化,可以使用服务端渲染(ssr),加载首屏时直接渲染服务端返回的html,避免因计算样式或加载js脚本影响加载速度; 3. 减少页面的重排和重绘,尽量避免使用table,因为table中一个小的变化会引起整个页面的重排; 4. 使用缓存,当资源未发生改变时,使用本地缓存可以提高用户体验; 5. 开启gzip加速,前端资源通过gzip打包放在服务器上,减少文件体积,加快获取速度。
C:决定使用什么优化技术:分析网站或应用的性能瓶颈。Web安全
Q:解释跨站脚本攻击(XSS)和跨站请求伪造(CSRF)的区别。你会如何防御这两种攻击?
A: XSS是通过在url、文本字段中注入script脚本实现的,比如在访问页面网址时携带参数,参数内包含script脚本,页面通过参数解析,执行恶意脚本;或者在评论区内提交一段script脚本,看到的用户都会遭受攻击;
CSRF是利用用户的授权信息去伪造请求,比如引导用户点击危险网站,恶意脚本会利用用户的身份认证去伪造危险行为。
为了防范XSS,可以对js代码进行转义、过滤用户输入等;防范CSRF可以防止页面注入iframe、使用csrf token验证等。
1. 实现一个 Promise.all
题目:请手写实现一个 Promise.all
函数,该函数接收一个 Promise 对象的数组作为参数,并返回一个新的 Promise 实例。新的 Promise 实例在所有输入 Promise 都成功解决时解决,返回值是一个包含所有输入 Promise 解决值的数组。如果任何一个输入 Promise 被拒绝,新的 Promise 立即拒绝,拒绝的原因是第一个拒绝的 Promise 的原因。
function promiseAll(promises) {
let result = new Array(promises.length);
let success = 0;
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
promise
.then((res) => {
result[index] = res;
success++;
if (success === promises.length) {
resolve(result);
}
})
.catch((e) => {
reject(e);
});
});
});
}
function promiseAllSettled(promises) {
let result = new Array(promises.length);
let count = 0;
return new Promise((resolve, reject) => {
promises.forEach((promise, index) => {
promise
.then((res) => {
result[index] = { state: "fulfilled", value: res };
count++;
if (count === promises.length) {
resolve(result);
}
})
.catch((err) => {
result[index] = { state: "rejected", err };
count++;
if (count === promises.length) {
resolve(result);
}
});
});
});
}
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p1");
}, 1000);
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p2");
}, 2000);
});
let p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("p3");
}, 3000);
});
promiseAll([p1, p2, p3])
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
promiseAllSettled([p1, p2, p3])
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
2. 实现一个简单的路由
题目:在不使用任何前端路由库的情况下,如何实现一个简单的前端路由(SPA)功能?请描述你的思路,并给出基本的实现代码。
3. 实现防抖(Debounce)函数
题目:请实现一个防抖(Debounce)函数。防抖函数接收一个函数和等待时间作为参数,并返回一个新的函数。返回的新函数在被连续调用时,只有在等待时间过去后才会执行原函数,如果在等待时间内再次被调用,则重新计时。
function debounce(func, wait) {
let timeout = null;
return function () {
clearTimeout(timeout);
let arg = arguments;
let _this = this;
timeout = setTimeout(() => {
func.apply(_this, arg);
}, wait);
};
}
let add = (a, b) => {
console.log(a + b);
};
const debounceAdd = debounce(add, 1000);
setTimeout(() => {
debounceAdd(1, 2);
}, 200);
setTimeout(() => {
debounceAdd(1, 2);
}, 200);
4. 实现一个简单的模板引擎
题目:实现一个简单的模板引擎,它能够将类似于 "Hello, {{name}}!"
的模板字符串和一个对象如 {name: "World"}
作为输入,并输出 "Hello, World!"
。请描述你的实现思路,并给出代码。
5. CSS 垂直居中的方法
题目:请列举至少三种使得一个元素在其父元素中垂直居中的方法,并简要描述每种方法的原理和使用场景。
- 深拷贝
const deepClone = (obj) => {
if (obj === null || typeof obj !== "object") return obj;
let result = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === "object") {
result[key] = deepClone(obj[key]);
} else {
result[key] = obj[key];
}
}
}
};
- 手写instanceOf
const myInstance = (left, right) => {
if (left !== "object" || left === null) return false;
let proto = Object.getPrototypeOf(left);
while (proto) {
if (proto === right.prototype) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
};
- 手写new
function myNew(fn, ...args) {
const obj = {};
obj.__proto__ = fn.prototype;
let result = fn.apply(fn, args);
return result instanceof Object ? result : obj;
}
- 实现简单ajax
function ajax(options) {
const xhr = new XMLHttpRequest();
options = options || {};
options.type = (options.type || "GET").toUpperCase();
options.dataType = options.dataType || "json";
const params = options.data;
if (options.type === "GET") {
xhr.open("GET", options.url + "?" + params, true);
xhr.send(null);
} else if (options.type === "POST") {
xhr.open("POST", options.url, true);
xhr.send(params);
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
const status = xhr.status;
if (status >= 200 && status < 300) {
options.success && options.success(xhr.responseText, xhr.responseXML);
} else {
options.fail && options.fail(status);
}
}
};
}
ajax({
url: "http://localhost:3000/api",
type: "GET",
data: {
name: "zhangsan",
age: 18,
},
dataType: "json",
success: function (response, xml) {
console.log(response);
},
fail: function (status) {
console.log(status);
},
});
- 函数柯里化
const curry = function (fn) {
return function curried(...args) {
if (args.length > fn.length) {
return function () {
return curried(...args.concat([...arguments]));
};
}
return fn(...args);
};
};
- 节流和防抖
function debounce(func, wait) {
let timeout = null;
return function () {
let args = arguments;
let _this = this;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(_this, args);
}, wait);
};
}
function throttle(func, wait) {
let timeout = null;
return function () {
let args = arguments;
let _this = this;
if (!timeout) {
timeout = setTimeout(() => {
func.apply(_this, args);
timeout = null;
}, wait);
}
};
}
function throttle(func, wait) {
let oldtime = Date.now();
return function () {
let args = arguments;
let _this = this;
let newtime = Date.now();
if (newtime - oldtime >= wait) {
func.apply(_this, args);
oldtime = Date.now();
}
};
}
- 并查集
function createUnionFind(size) {
const parent = new Array(size).fill(0).map((_, i) => i);
function find(x) {
if (parent[x] === x) {
return x;
}
parent[x] = find(parent[x]);
return parent[x];
}
function union(x, y) {
let rootX = find(x);
let rootY = find(y);
if (rootX !== rootY) {
parent[rootX] = rootY;
}
}
function connected(x, y) {
return find(x) === find(y);
}
return {
find,
union,
connected,
};
}